import pandas as pd
import os
import matplotlib.pyplot as plt
import numpy as np
import info
from datetime import date, timedelta
from matplotlib.ticker import MultipleLocator
from GetExternalDataSourceModule_CLIENT import GetExternalDataSource_CLIENT
class Evaluation:
    def __init__(self, product_name):
        self.product_name = product_name
        self.sever_connector = GetExternalDataSource_CLIENT()

    def plot_record(self, trade_record):

        fig = plt.figure(figsize=(15, 10))
        ax1 = fig.subplots(1, 1)
        trade_record_daily_dict = {}
        for index, row in trade_record.iterrows():
            start_date = row['datetime_start']
            end_date = row['datetime_end']
            delta = end_date - start_date  # returns timedelta
            date_num = delta.days
            if date_num==0:
                trade_record_daily_dict[index] = pd.Series({
                    'end_price': row['end_price'],
                    'init_price': row['init_price'],
                    'real_end_price': row['real_end_price'],
                    'real_init_price': row['real_init_price'],
                    'contract_name': row['contract_name'],
                    'trade_signal':1,
                    'position':row['position'],
                    'ret':row['ret'],
                    'real_ret': row['real_ret'],
                    'trade_date':row['trade_date'],
                    'holding': 0
                })
            elif date_num>0:
                between_dates = []
                for i in range(delta.days):
                    day = start_date + timedelta(days=i)
                    between_dates.append(day)

                clearing_price_dict = {}
                for count, date in enumerate(between_dates):
                    date_str = str(date)[:10]
                    daily_trade_data = self.sever_connector.get_file(row['contract_name'], info.DATAPATH.server_root_path + '/' + 'future_trade_data' + '/' + 'dailyBar' + '/' + row['ProductName']
                                                                     + '/' + row['contract_name'] + '.pkl')
                    if date_str in daily_trade_data.date.tolist():
                        if count==0:
                            clearing_price_dict[row['datetime_start']] = \
                                daily_trade_data.loc[daily_trade_data.date == date_str, 'syl1'].values[0]
                        elif count==date_num:
                            clearing_price_dict[row['datetime_end']] = \
                            daily_trade_data.loc[daily_trade_data.date == date_str, 'syl1'].values[0]
                        else:
                            clearing_price_dict[date] = daily_trade_data.loc[daily_trade_data.date==date_str, 'syl1'].values[0]


                clearing_price = pd.Series(clearing_price_dict)
                clearing_price = clearing_price.to_frame('end_price')
                clearing_price_tmp = clearing_price['end_price']
                clearing_price['init_price'] = np.NaN
                clearing_price.loc[end_date, 'end_price'] = row['end_price']
                clearing_price['init_price'] = clearing_price.shift(1)['end_price']
                clearing_price.loc[start_date, 'init_price'] = row['init_price']

                clearing_price.loc[:, 'real_end_price'] = clearing_price_tmp
                clearing_price['real_init_price'] = np.NaN
                clearing_price.loc[end_date, 'real_end_price'] = row['real_end_price']
                clearing_price['real_init_price'] = clearing_price.shift(1)['real_end_price']
                clearing_price.loc[start_date, 'real_init_price'] = row['real_init_price']

                clearing_price['contract_name'] = row['contract_name']
                clearing_price['trade_signal'] = 0
                clearing_price.iloc[0, clearing_price.columns=='trade_signal'] = 1
                clearing_price['position'] = row['position']
                clearing_price['holding'] = 1
                clearing_price.iloc[-1, clearing_price.columns=='holding'] = 0
                clearing_price['trade_date'] = row['trade_date']

                if row['status'] == 'On':
                    clearing_price.iloc[-1, clearing_price.columns=='read_end_price'] = \
                        daily_trade_data.loc[daily_trade_data.date == str(clearing_price.index[-1])[:10], 'syl1'].values[0]
                    clearing_price.iloc[-1, clearing_price.columns=='end_price'].iloc[-1] = \
                        daily_trade_data.loc[daily_trade_data.date == str(clearing_price.index[-1])[:10], 'syl1'].values[0]
                    clearing_price.iloc[-1, clearing_price.columns=='holding'] = 1

                if row['position']=='long':
                    clearing_price['ret'] = clearing_price['end_price']/clearing_price['init_price'] - 1
                    clearing_price['real_ret'] = clearing_price['real_end_price'] / clearing_price['real_init_price'] - 1
                elif row['position']=='short':
                    clearing_price['ret'] = 1 - clearing_price['end_price']/clearing_price['init_price']
                    clearing_price['real_ret'] = 1 - clearing_price['real_end_price'] / clearing_price['real_init_price']
                for index_clearing, row_clearing in clearing_price.iterrows():
                    trade_record_daily_dict[index_clearing]=row_clearing



        trade_record_daily_df = pd.DataFrame(trade_record_daily_dict).T
        trade_record_daily_df.index = pd.to_datetime(trade_record_daily_df['trade_date'].tolist()).tolist()

        trade_record_daily = trade_record_daily_df.resample('1D').agg({'ret':'sum',
                                                                       'real_ret':'sum',
                                                                       'trade_signal':'sum',
                                                                       })
        trade_record_daily = trade_record_daily.rename(columns={'trade_unit': 'open_times'})
        trade_record_daily.index = [str(x)[:10] for x in trade_record_daily.index.tolist()]
        trade_record_daily = trade_record_daily.replace(0, np.NaN).dropna(how='all').fillna(0)

        real_ret = trade_record_daily.real_ret
        real_ret_cumsum = real_ret.cumsum()
        ret = trade_record_daily.ret
        ret_cumsum = ret.cumsum()
        drawdown, drawdown_max, drawdown_start_date, drawdown_end_date, drawdown_last_days = self.get_drawdown(trade_record_daily.real_ret)
        drawdown_max = str(round(drawdown.min()*100, 2))
        ax1.plot(real_ret_cumsum, label='cumsum_ret_after_fee')
        plt.legend()
        ax1.plot(ret_cumsum, label='cumsum_ret_no_fee')
        ax1.bar(drawdown.index.tolist(), drawdown, color='grey', alpha=0.5, label='Dynamic Drawdown')

        # ax2.bar(trade_record_daily.index.tolist(), trade_record_daily.trade_signal_long, alpha=0.5,
        #         label='Short Trade Count')
        # ax2.bar(trade_record_daily.index.tolist(), -trade_record_daily.trade_signal_short, alpha=0.5,
        #         label='Short Trade Count')
        # ax2.legend()
        annual_ret = str(round(self.cal_annual_ret(trade_record_daily.ret)*100, 2))
        annual_ret_after_fee = str(round(self.cal_annual_ret(trade_record_daily.real_ret)*100, 2))
        ret_after_fee_per_trade = str(round(trade_record_daily.real_ret.sum()/trade_record_daily.trade_signal.sum()*100, 4))
        trade_count_per_day = str(
            round(trade_record_daily.trade_signal.sum()/len(trade_record_daily.trade_signal), 2))
        Sharpe_Ratio = str(round(self.cal_Sharpe(trade_record_daily.real_ret), 2))
        Calmar_ratio = str(round(float(annual_ret_after_fee) / abs(float(drawdown_max)), 2))
        Sortino_ratio = str(round(self.cal_SortinoRatio(trade_record_daily.real_ret, annRiskFreeRet=0), 2))

        ax1.set_title('Annual Ret: ' + annual_ret + '%' +
                      ' Annual Ret After Fee: ' + annual_ret_after_fee + '%' +
                      ' Return per Trade After Fee: ' + ret_after_fee_per_trade + '%' +
                      ' Trade Count per Day: ' + trade_count_per_day +
                      '\n' +
                      ' Sharpe Ratio After Fee: ' + Sharpe_Ratio +
                      ' Calmar Ratio After Fee: ' + Calmar_ratio +
                      ' Sortino Ratio After Fee: ' + Sortino_ratio +
                      '\n' +
                      ' Max Drawdown: ' + drawdown_max + '%' +
                      ' Max Drawdown start: ' + drawdown_start_date +
                      ' Max Drawdown end: ' + drawdown_end_date +
                      ' Max Drawdown Last Days: ' + drawdown_last_days,
                      fontsize=10)

        xmajorLocator = MultipleLocator(int(len(real_ret) / 20 if len(real_ret) > 20 else 1))
        ax1.xaxis.set_major_locator(xmajorLocator)
        for tick in ax1.get_xticklabels():
            tick.set_rotation(15)
        # ax2.xaxis.set_major_locator(xmajorLocator)
        # for tick in ax2.get_xticklabels():
        #     tick.set_rotation(15)
        ax1.legend()
        plt.show()

    def get_drawdown(self, ret_series):
        drawdown_series = ret_series.cumsum() - ret_series.cumsum().cummax()
        drawdown_max = drawdown_series.min()
        down_date = drawdown_series[drawdown_series == drawdown_max].index[0]
        start_date = ret_series.cumsum()[ret_series.cumsum() == ret_series.cumsum()[:down_date].max()].index[0]
        if (ret_series.cumsum() > ret_series.cumsum()[:down_date].max()).sum() != 0:
            end_date = ret_series.cumsum()[ret_series.cumsum() > ret_series.cumsum()[:down_date].max()].index[0]
        else:
            end_date = 'Not End'
        max_drawdown_last_days = str(len(ret_series.cumsum()[start_date:end_date]))
        return drawdown_series, drawdown_max, start_date, end_date, max_drawdown_last_days

    def cal_annual_ret(self, retSeries):
        '''
        :param return_list: 收益序列
        :return: 年化收益
        '''
        D = len(retSeries)
        Anual_Ret = (1 + retSeries).prod() ** (252 / D) - 1
        return Anual_Ret

    def cal_std(self, retSeries):
        return np.std(retSeries) * np.sqrt(252)

    def cal_Sharpe(self, return_list):  # return_list = alpha_Dailyret
        '''
        :param return_list: 收益序列
        :return: 夏普比率
        '''
        return_stdev = np.std(return_list) * np.sqrt(252)
        AnnualRet = self.cal_annual_ret(return_list)
        sharpe_ratio = AnnualRet/ return_stdev
        #默认252个工作日
        return sharpe_ratio

    def cal_SortinoRatio(self, retSeries, annRiskFreeRet=0.0):
        """
        :param retSeries: 收益序列
        :param retRiskFree: 无风险收益
        :return:
        """
        annRet = self.cal_annual_ret(retSeries)
        alpha = retSeries - self.cal_daily_return(annRiskFreeRet)
        annDownDev = self.cal_DownDev(alpha)
        Sortinoratio = (annRet - annRiskFreeRet) / annDownDev
        return Sortinoratio

    def cal_daily_return(self, annRet):
        '''
        :param return_list: 收益序列
        :return: 年化收益
        '''
        dailyRet = ((annRet + 1) ** (1 / 252) - 1)
        return dailyRet

    def cal_DownDev(self, retSeries):
        downRet = retSeries[retSeries < 0]
        numReturnPeriods = len(retSeries)
        downDev = np.sqrt(np.sum(np.square(downRet)) * 252 / numReturnPeriods)
        return downDev

    def align_yaxis(self, ax1, v1, ax2, v2):
        """adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
        _, y1 = ax1.transData.transform((0, v1))
        _, y2 = ax2.transData.transform((0, v2))
        inv = ax2.transData.inverted()
        _, dy = inv.transform((0, 0)) - inv.transform((0, y1 - y2))
        miny, maxy = ax2.get_ylim()
        ax2.set_ylim(miny + dy, maxy + dy)
